package io.github.annyylxyms.gyncache.core;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.val;

import java.util.*;
import java.util.stream.Stream;

/**
 * cache entries.
 * @author Anny
 * @date 2025/1/2 15:44
 */
public class GynCache {
    //只支持存String类型的值
    //Map<String, String> map = new HashMap<>();

    //这里设计支持其他类型的值，有两种办法，一种是？还有一种用CacheEntry类
    Map<String, CacheEntry<?>> map = new HashMap<>();

    // =============== 1. String ===================
    public String get(String key) {
        //显式返回String,用getValue取
        CacheEntry<String> entry = (CacheEntry<String>) map.get(key);
        return entry.getValue();
    }

    public void set(String key, String value) {
        map.put(key, new CacheEntry<>(value));
    }

    /**
     * 可以支持删除多个
     * @param keys
     * @return
     */
    public int del(String... keys) {
        //filter(x -> x != null) 可以优化写成filter(Objects::nonNull) 过滤掉删除失败（即返回 null）的情况
        //Arrays.stream(keys).map(map::remove).filter(x -> x != null).count();
        //过滤掉删除失败（即返回 null）的情况。
        return keys == null ? 0 : (int) Arrays.stream(keys)
                .map(map::remove).filter(Objects::nonNull).count();
    }

    public int exists(String... keys) {
        //filter(x -> x) 因为map.containsKey(x)返回的是boolean,故filter(x -> x == true)就可以直接简化为filter(x -> x)
        return keys == null ? 0 : (int) Arrays.stream(keys)
                .map(map::containsKey).filter(x -> x).count();
    }

    /**
     * 可以在一次请求中获取多个键的值，从而提高效率
     * 命令格式：MGET key1 [key2 ...]
     * 功能：返回一个包含所有给定键对应值的列表。
     * 返回值：一个数组，包含每个给定键对应的值。如果某个键不存在，则返回 nil。
     * @param keys
     * @return
     */
    public String[] mget(String... keys) {
        //String[]也算一个类型，也可以用::new来构造
        return keys == null ? new String[0] : Arrays.stream(keys)
                .map(this::get).toArray(String[]::new);
    }

    /**
     * mset(key1, val1, key2, val2, ...)
     * @param keys
     * @param vals
     */
    public void mset(String[] keys, String[] vals) {
        if (keys == null || keys.length == 0) return;
        for (int i = 0; i < keys.length; i++) {
            set(keys[i], vals[i]);
        }
    }

    public int incr(String key) {

        String str = get(key);
        int val = 0;
        try {
            if (str != null) {
                val = Integer.parseInt(str);
            }
            val++;
            set(key, String.valueOf(val));
        } catch (NumberFormatException nfe) {
            //NumberFormatException是RuntimeException，所以不用在函数声明上写东西 throws NumberFormatException。
            throw nfe;
        }
        return val;
    }

    public int decr(String key) {

        String str = get(key);
        int val = 0;
        try {
            if (str != null) {
                val = Integer.parseInt(str);
            }
            val--;
            set(key, String.valueOf(val));
        } catch (NumberFormatException nfe) {
            //NumberFormatException是RuntimeException，所以不用在函数声明上写东西 throws NumberFormatException。
            throw nfe;
        }
        return val;
    }

    public Integer strlen(String key) {
        return get(key) == null ? null : get(key).length();
    }
    // ================= 1. String end ===================

    // ================= 2. list ===================

    /**
     * LPUSH 是 Redis 中用于操作列表（List）数据类型的命令，用于将一个或多个值插入到列表的开头（左侧）
     * 命令格式：LPUSH key value [value ...]
     * 功能：将一个或多个值插入到列表的开头。如果键不存在，则创建一个新的列表并插入值。如果键存在且不是列表类型，则返回错误。
     * 返回值：
     * 如果键不存在，则返回 0。
     * 如果键存在且是列表类型，则返回列表的长度（即插入了多少个值）。
     * 假设有一个列表 mylist，初始为空：
     * 执行命令：LPUSH mylist "one" "two" "three"
     * 返回值：3
     * @param key
     * @param vals
     * @return
     */
    public Integer lpush(String key, String[] vals) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) {
            //如果没取到，new一个list ，这里用LinkedList更好，因为可以按顺序插入，还有封装的addfirst 、addlast方法可以直接用
            entry = new CacheEntry<>(new LinkedList<>());
            this.map.put(key, entry);
        }
        LinkedList<String> exist = entry.getValue();
        Arrays.stream(vals).forEach(exist::addFirst);
        return vals.length;
    }


    /**
     * 用于移除并返回列表中的第一个元素
     * 命令格式：LPOP key [count]
     * 功能：移除并返回列表中的第一个元素。如果列表为空，则返回 nil。
     * 返回值：移除的元素，如果列表为空，则返回 nil。
     * 假设有一个列表 mylist，初始为 ["one", "two", "three"]：
     * 执行命令：LPOP mylist
     * 返回值："one"
     * count 参数用于指定移除的元素数量，默认为 1。如果 count 为正数，则从左侧开始移除元素；如果 count 为负数，则从右侧开始移除元素。如果 count 为 0，则不移除任何元素，只是返回移除的元素数量。
     * 假设有一个列表 mylist，初始为 ["one", "two", "three"]：
     * 执行命令：LPOP mylist 2
     * 返回值：["one", "two"]
     * @param key
     * @param count
     * @return
     */
    public String[] lpop(String key, int count) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) return null;
        LinkedList<String> exist = entry.getValue();
        if (exist == null) return null;

        //命令要删3个，实际只有2个，比较返回最小值2
        int len = Math.min(count, exist.size());
        //创建一个返回用的数组，赋值被移除的数组内容
        String[] ret = new String[len];
        int index = 0;
        while (index < len) {
            ret[index++] = exist.removeFirst();
        }
        return ret;
    }

    /**
     * RPUSH 是 Redis 中用于操作列表（List）数据类型的命令，用于将一个或多个值插入到列表的尾部（右侧）
     * @param key
     * @param vals
     * @return
     */
    public Integer rpush(String key, String[] vals) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) {
            //如果没取到，new一个list ，这里用LinkedList更好，因为可以按顺序插入，还有封装的addfirst 、addlast方法可以直接用
            entry = new CacheEntry<>(new LinkedList<>());
            this.map.put(key, entry);
        }
        LinkedList<String> exist = entry.getValue();

        //下面这两种写法都是默认从最后往里加数据,注意List.of(这里不能为空)
        //Arrays.stream(vals).forEach(exist::addLast);
        exist.addAll(List.of(vals));
        return vals.length;
    }

    public String[] rpop(String key, int count) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) return null;
        LinkedList<String> exist = entry.getValue();
        if (exist == null) return null;

        //命令要删3个，实际只有2个，比较返回最小值2
        int len = Math.min(count, exist.size());
        //创建一个返回用的数组，赋值被移除的数组内容
        String[] ret = new String[len];
        int index = 0;
        while (index < len) {
            ret[index++] = exist.removeLast();
        }
        return ret;
    }


    public Integer llen(String key) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) return 0;
        LinkedList<String> exist = entry.getValue();
        if (exist == null) return 0;
        return exist.size();
    }

    /**
     * LINDEX 是 Redis 中用于操作列表（List）数据类型的命令，用于获取列表中指定索引位置的元素。
     * 功能：获取列表中指定索引位置的元素。索引位置可以是正数（从 0 开始），也可以是负数（表示从列表尾部开始）。如果索引位置超出范围，则返回 nil。
     * 返回值：如果索引位置存在元素，则返回该元素的值；如果索引位置超出范围，则返回 nil。
     * 假设有一个列表 mylist，初始为 ["one", "two", "three"]：
     * 执行命令：LINDEX mylist 1
     * 返回值："two"
     * @param key
     * @param index
     * @return
     */
    public String lindex(String key, int index) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) return null;
        LinkedList<String> exist = entry.getValue();
        if (exist == null) return null;
        if (index >= exist.size()) return null;
        return exist.get(index);
    }

    /**
     * LRANGE 是 Redis 中用于操作列表（List）数据类型的命令，用于获取列表中指定范围的元素。
     * 功能：获取列表中指定范围的元素。范围由 start 和 end 两个
     * 参数指定，包括 start 和 end 两个索引位置。索引位置可以是正数（从 0 开始），也可以是负数（表示从
     * 列表尾部开始）。如果 start 或 end 超出范围，则返回空列表。
     * 返回值：如果 start 和 end 都在范围内，则返回一个包含指定范围的元素的列表；如果 start 或 end 超出范围，则返回空列表。
     * 假设有一个列表 mylist，初始为 ["one", "two", "three", "four", "five"]：
     * 执行命令：LRANGE mylist 1 3
     * 返回值：["two", "three", "four"]
     * @param key
     * @param start
     * @param end
     * @return
     */
    public String[] lrange(String key, int start, int end) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (entry == null) return null;
        LinkedList<String> exist = entry.getValue();
        if (exist == null) return null;
        int size = exist.size();
        if (start >= size) return null;
        if (end >= size) end = size - 1;

        //end-start+1 不能超过size
        //为啥要+1？  比如start=1 end=5,取得是从第一个到五个元素，实际上取了5个元素，5-1=4，4+1=5
        //再比如 start=1 end=5 ，实际取第一个，所以要+1
        int len = Math.min(size, end - start + 1);
        //创建返回数组
        String[] ret = new String[len];
        for (int i = 0; i < len; i++) {
            ret[i] = exist.get(start + i);
        }
        return ret;
    }


    // ================= 2. list end ===================


    // ================= 3. set     ===================

    /**
     * SADD 是 Redis 中用于操作集合（Set）数据类型的命令，用于将一个或多个成员添加到集合中。如果成员已经存在于集合中，则不会重复添加。
     * 命令格式：SADD key member [member ...]
     * 功能：将一个或多个成员添加到集合中。如果键不存在，则创建一个新的集合。
     * 返回值：被成功添加到集合中的新成员数量（即不包括已存在的成员）。
     * 假设有一个集合 myset，初始为空：
     * 执行命令：SADD myset "one" "two" "three"
     * 返回值：3
     *
     * @param key
     * @param vals
     * @return
     */
    public Integer sadd(String key, String[] vals) {
        //和lpush不同的是拿的是LinkedHashSet，不是LinkedList
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (entry == null) {
            //如果没取到，new一个list ，这里用LinkedList更好，因为可以按顺序插入，还有封装的addfirst 、addlast方法可以直接用
            entry = new CacheEntry<>(new LinkedHashSet<>());
            this.map.put(key, entry);
        }
        LinkedHashSet<String> exist = entry.getValue();
        //下面两种写法都可以，效果一样
        //Arrays.stream(vals).forEach(exist::add);
        exist.addAll(Arrays.asList(vals));

        //这里有个问题，返回的应该是执行成功的数量，而不是所有vals的和
        return vals.length;
    }

    /**
     * SMEMBERS 是 Redis 中用于操作集合（Set）数据类型的命令，用于返回集合中的所有成员。
     * 命令格式：SMEMBERS key
     * 功能：返回集合中所有的成员。
     * 返回值：
     * 如果键存在且是集合类型，返回一个包含集合中所有成员的列表。
     * 如果键不存在或不是集合类型，返回空列表 []。
     * 示例
     * 假设有一个集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：SMEMBERS myset
     * 返回值：["one", "two", "three"]
     * @param key
     * @return
     */
    public String[] smembers(String key) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashSet<String> exist = entry.getValue();
        return exist.toArray(String[]::new);
    }

    /**
     * 集合长度
     * SCARD 是 Redis 中用于操作集合（Set）数据类型的命令，用于获取集合中成员的数量。
     * 命令格式：SCARD key
     * 功能：返回集合中成员的数量。
     * 返回值：
     * 如果键存在且是集合类型，返回集合中成员的数量。
     * 如果键不存在或不是集合类型，返回 0。
     * 示例
     * 假设有一个集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：SCARD myset
     * 返回值：3
     * @param key
     * @return
     */
    public Integer scard(String key) {
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<?> exist = (LinkedHashSet<?>) entry.getValue();
        return exist.size();
    }

    /**
     * SISMEMBER 是 Redis 中用于操作集合（Set）数据类型的命令，用于检查一个成员是否存在于集合中。
     * 命令格式：SISMEMBER key member
     * 功能：检查指定的成员是否存在于集合中。
     * 返回值：
     * 1 如果成员存在于集合中。
     * 0 如果成员不存在于集合中或键不存在。
     * 示例
     * 假设有一个集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：SISMEMBER myset "two"
     * 返回值：1
     * @param key
     * @return
     */
    public Integer sismember(String key, String val) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (entry == null) return 0;
        LinkedHashSet<String> exist = entry.getValue();
        return exist.contains(val) ? 1 : 0;
    }

    Random random = new Random();

    /**
     * SPOP 是 Redis 中用于操作集合（Set）数据类型的命令，用于随机移除并返回集合中的一个或多个成员。
     * 命令格式：
     * SPOP key：移除并返回集合中的一个随机成员。
     * SPOP key count：移除并返回集合中的 count 个随机成员（Redis 3.2 及以上版本支持）。
     * 功能：从集合中随机移除一个或多个成员，并返回被移除的成员。
     * 返回值：
     * 如果键存在且是集合类型，返回被移除的成员。如果指定了 count 参数，则返回包含多个成员的列表。
     * 如果键不存在或不是集合类型，返回 nil 或空列表
     * 示例
     * 假设有一个集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：SPOP myset
     * 返回值："one"
     * 执行命令：SPOP myset 2
     * 返回值：["two", "three"]
     * @param key
     * @param count
     * @return
     */
    public String[] spop(String key, int count) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashSet<String> exist = entry.getValue();
        if (exist == null) return null;

        //命令要删3个，实际只有2个，比较返回最小值2
        int len = Math.min(count, exist.size());
        //创建一个返回用的数组，赋值被移除的数组内容
        String[] ret = new String[len];
        int index = 0;
        while (index < len) {
            //exist转为数组，随机取一个，然后删除，返回删除元素
            String[] array = exist.toArray(String[]::new);
            String obj = array[random.nextInt(exist.size())];
            exist.remove(obj);
            ret[index++] = obj;
        }
        return ret;
    }


    /**
     * SREM 是 Redis 中用于操作集合（Set）数据类型的命令，用于从集合中移除一个或多个成员。
     * 命令格式：SREM key member [member ...]
     * 功能：从集合中移除指定的一个或多个成员。
     * 返回值：被成功移除的成员数量（即不包括不存在的成员）。
     * 示例
     * 假设有一个集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：SREM myset "two" "three"
     * 返回值：2
     * @param key
     * @param vals
     * @return
     */
    public Integer srem(String key, String[] vals) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (entry == null) return 0;
        LinkedHashSet<String> exist = entry.getValue();

        //remove方法返回boolean类型，所以用filter x->x筛出true（true即remove成功）的数据，再count一下。
        return vals == null ? 0 : (int) Arrays.stream(vals)
                .map(exist::remove).filter(x -> x).count();
    }

    // ================= 3. set end ===================


    // ================= 4. hash     ===================
    public Integer hdel(String key, String[] hkeys) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashMap<String, String> exist = entry.getValue();

        //remove返回的是一个对象，所以非空才算删除成功，所以用filter x->x筛出true（true即remove成功）的数据，再count一下。
        return hkeys == null ? 0 : (int) Arrays.stream(hkeys)
                .map(exist::remove).filter(Objects::nonNull).count();
    }

    public Integer hexists(String key, String hkey) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashMap<String, String> exist = entry.getValue();
        return exist.containsKey(hkey) ? 1 : 0;
    }

    /**
     * HGETALL 是 Redis 中用于操作哈希（Hash）数据类型的
     * 命令，用于获取哈希表中的所有字段和值。
     * 命令格式：HGETALL key
     * 功能：获取哈希表中的所有字段和值。
     * 返回值：
     * 如果哈希存在且不为空，返回一个包含所有字段和值的列表。
     * 如果哈希不存在或为空，返回空列表。
     * 示例
     * 假设有一个哈希 myhash， 包含以下字段和值：
     * "field1", "value1", "field2", "value2"
     * 执行命令：HGETALL myhash
     * 返回值：["field1", "value1", "field2", "value2"]
     * @param key
     * @return
     */
    public String[] hgetall(String key) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashMap<String, String> exist = entry.getValue();
        //遍历map，将key和value拼接成数组返回(巧妙写法！)
        return exist.entrySet().stream()
                .flatMap(e -> Stream.of(e.getKey(), e.getValue()))
                .toArray(String[]::new);
    }

    /**
     * HGET 是 Redis 中用于操作哈希（Hash）数据类型的命令，用于获取哈希字段的值。
     * 命令格式：HGET key field
     * 功能：获取哈希字段的值。
     * 返回值：
     * 如果哈希字段存在，返回字段
     * 如果哈希字段不存在，返回 nil。
     * 示例
     * 假设有一个哈希 myhash，包含以下字段：
     * "field1", "value1", "field2", "value2"
     * 执行命令：HGET myhash "field1"
     * 返回值："value1"
     * @param key
     * @param hkey
     * @return
     */
    public String hget(String key, String hkey) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashMap<String, String> exist = entry.getValue();
        return exist.get(hkey);
    }

    /**
     *  HLEN 是 Redis 中用于操作哈希（Hash）数据类型的命令，用于获取哈希表中字段的数量。
     *  功能：获取哈希表中字段的数量。
     *  返回值：
     *  如果哈希存在且不为空，返回哈希表中字段的数量。
     *  如果哈希不存在或为空，返回 0。
     *  示例
     *  假设有一个哈希 myhash，包含以下字段：
     *  "field1", "value1", "field2", "value2"
     *  执行命令：HLEN myhash
     *  返回值：2
     * @param key
     * @return
     */
    public Integer hlen(String key) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return 0;
        LinkedHashMap<String, String> exist = entry.getValue();
        return exist.size();
    }

    /**
     * HMGET 是 Redis 中用于操作哈希（Hash）数据类型的命令，用于获取多个哈希字段的值。
     * 命令格式：HMGET key field [field ...]
     * 功能：获取多个哈希字段的值。
     * 返回值：
     * 如果哈希存在且不为空，返回一个包含多个字段的值的列表。
     * 如果哈希不存在或为空，返回空列表。
     * 示例
     * 假设有一个哈希 myhash，包含以下字段：
     * "field1", "value1", "field2", "value2"
     * 执行命令：HMGET myhash "field1" "field2"
     * 返回值：["value1", "value2"]
     * @param key
     * @param hkeys
     * @return
     */
    public String[] hmget(String key, String[] hkeys) {
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) return null;
        LinkedHashMap<String, String> exist = entry.getValue();

        //String[]也算一个类型，也可以用::new来构造
        return hkeys == null ? new String[0] : Arrays.stream(hkeys)
                .map(exist::get).toArray(String[]::new);
    }

    /**
     * HSET 是 Redis 中用于操作哈希（Hash）数据类型的命令，用于设置哈希字段的值。
     * 命令格式：HSET key field value [field value ...]
     * 功能：设置哈希字段的值。如果字段已经存在，则更新其值。
     * 返回值
     * 如果字段已经存在，则更新其值，并返回 0。如果字段不存在，则创建并设置值，并返回 1。
     * 示例
     * 假设有一个哈希 myhash，包含以下字段：
     * "field1", "value1", "field2", "value2"
     * 执行命令：HSET myhash "field3" "value3"
     * 返回值：1
     * @param key
     * @param hkeys
     * @param hvals
     * @return
     */
    public Integer hset(String key, String[] hkeys, String[] hvals) {
        if (hkeys == null || hkeys.length == 0) return 0;
        if (hvals == null || hvals.length == 0) return 0;
        CacheEntry<LinkedHashMap<String, String>> entry = (CacheEntry<LinkedHashMap<String, String>>) map.get(key);
        if (entry == null) {
            entry = new CacheEntry<>(new LinkedHashMap<>());
            this.map.put(key, entry);
        }
        LinkedHashMap<String, String> exist = entry.getValue();
        for (int i = 0; i < hkeys.length; i++) {
            exist.put(hkeys[i], hvals[i]);
        }
        return hkeys.length;
    }

    // ================= 4. hash end ===================

    // ================= 5. zset     ===================

    /**
     * ZADD 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于向有序集合中添加成员。
     * 命令格式：ZADD key score member [score member ...]
     * 功能：向有序集合中添加成员。如果成员已经存在，则更新其分数。
     * 参数说明：
     * key：有序集合的键名。
     * score：成员的分数，用于排序。
     * member：成员的值。
     * 返回值：
     * 返回添加成员的数量。
     * 注意事项：
     * 如果键不存在，则创建一个有序集合，并添加成员。
     * 如果分数相同，则按照成员添加的顺序进行排序。
     * 示例：
     * 假设有序集合 myzset 中存在成员 "member1"，分数为 10，"member2"，分数为 20，"member3"，分数为 30。
     * 执行命令：ZADD myzset 40 "member4" 50 "member5" 60 "member6"
     * 返回值：3
     * 添加了 "member4"、"member5" 和 "member6"，返回值为 3。
     * @param key
     * @param vals
     * @param aDouble
     * @return
     */
    public Integer zadd(String key, String[] values, double[] scores) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (entry == null) {
            //如果没取到，new一个list ，这里用LinkedList更好，因为可以按顺序插入，还有封装的addfirst 、addlast方法可以直接用
            entry = new CacheEntry<>(new LinkedHashSet<>());
            this.map.put(key, entry);
        }
        LinkedHashSet<ZsetEntry> exist = entry.getValue();
        for (int i = 0; i < values.length; i++) {
            exist.add(new ZsetEntry(values[i], scores[i]));
        }
        //这里有个问题，返回的应该是执行成功的数量，而不是所有vals的和
        return values.length;
    }

    /**
     * ZCARD 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于获取有序集合的成员数量。
     * 命令格式：ZCARD key
     * 功能：返回有序集合的成员数量。
     * 返回值：有序集合的成员数量。
     * 注意事项：
     * 如果键不存在或不是有序集合类型，返回 0。
     * 示例：
     * 假设有序集合 myzset 中存在成员 "member1"，分数为 10，"member2"，分数为 20，"member3"，分数为 30。
     * 执行命令：ZCARD myzset
     * 返回值：3
     * @param key
     * @return
     */
    public Integer zcard(String key) {
        //当方法场景不用关注里面类型的时候，可以直接用泛型接收，不用强转了
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<?> exist = (LinkedHashSet<?>) entry.getValue();
        return exist.size();
    }

    /**
     * ZCOUNT 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于计算有序集合中分数在指定范围内的成员数量。
     * 命令格式：ZCOUNT key min max
     * 功能：返回有序集合中分数介于 min 和 max 之间的成员数量。
     * 参数说明：
     * key：有序集合的键名。
     * min：分数范围的下限（包含）。
     * max：分数范围的上限（包含）。
     * 返回值：分数介于 min 和 max 之间的成员数量。
     * 注意事项
     * 如果键不存在或不是有序集合类型，返回 0。
     * min 和 max 可以使用 -inf 和 +inf 分别表示负无穷和正无穷。
     * min 和 max 是包含关系，即 [min, max]。
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Integer zcount(String key, double min, double max) {
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<ZsetEntry> exist = (LinkedHashSet<ZsetEntry>) entry.getValue();
        return (int) exist.stream().filter(x -> x.getScore() >= min && x.getScore() <= max).count();
    }

    /**
     * ZRANK 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于返回指定成员在有序集合中的排名。排名是基于成员的分数从小到大排序的。
     * 命令格式：ZRANK key member
     * 功能：返回有序集合中指定成员的排名。排名从 0 开始。
     * 返回值：
     * 如果成员存在于有序集合中，返回其排名。
     * 如果成员不存在或键不存在，返回 nil。
     * @param key
     * @param val
     * @return
     */
    public Integer zrank(String key, String val) {
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<ZsetEntry> exist = (LinkedHashSet<ZsetEntry>) entry.getValue();
        Double zscore = zscore(key, val);
        if (zscore == null) return null;
        //所有比当前分数zscore小的都是排在前面的
        return (int) exist.stream().filter(x -> x.getScore() < zscore).count();
    }

    /**
     * ZREM 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于从有序集合中移除一个或多个成员。
     * 命令格式：ZREM key member [member ...]
     * 功能：从有序集合中移除指定的一个或多个成员。
     * 返回值：被成功移除的成员数量（即不包括不存在的成员）。
     * 示例
     * 假设有一个有序集合 myset，包含以下成员：
     * "one", "two", "three"
     * 执行命令：ZREM myset "two" "three"
     * 返回值：2
     * @param key
     * @param vals
     * @return
     */
    public Integer zrem(String key, String[] vals) {
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<ZsetEntry> exist = (LinkedHashSet<ZsetEntry>) entry.getValue();

        //removeIf返回的也是布尔类型，所以也用x -> x拿到true的内容
        return vals == null ? 0 : (int) Arrays.stream(vals)
                .map(x->exist.removeIf(y->y.getValue().equals(x)))
                .filter(x -> x).count();
    }

    /**
     * ZSCORE 是 Redis 中用于操作有序集合（Sorted Set）数据类型的命令，用于返回指定成员的分数。
     * 命令格式：ZSCORE key member
     * 功能：返回有序集合中指定成员的分数。
     * 返回值：
     * 如果成员存在于有序集合中，返回其分数。
     * 如果成员不存在或键不存在，返回 nil。
     * @param key
     * @param val
     * @return
     */
    public Double zscore(String key, String val) {
        CacheEntry<?> entry = map.get(key);
        if (entry == null) return null;
        LinkedHashSet<ZsetEntry> exist = (LinkedHashSet<ZsetEntry>) entry.getValue();
        return exist.stream().filter(x -> x.getValue().equals(val))
                .map(ZsetEntry::getScore).findFirst().orElse(null);
    }
    // ================= 5. zset end ===================

    /**
     * 此类用于放不同数据类型、时间戳、过期时间等内容
     * @param <T>
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class CacheEntry<T> {
        private T value;
//        private long expire;
//        private long createTime;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ZsetEntry {
        private String value;
        private double score;
    }
}
